use std::fs;
use std::io::Read;
use std::path::Path;
use std::collections::HashMap;
use tar::{Archive, Entry};
use log;
use flate2::read::{GzDecoder, MultiGzDecoder};
use lazy_static::lazy_static;
use color_eyre::Result;
use color_eyre::eyre::{self, WrapErr};

lazy_static! {
    pub static ref PACKAGE_KEY_MAPPING: std::collections::HashMap<&'static str, &'static str> = {
        let mut m = std::collections::HashMap::new();

		// Map APK field names to common field names based on gen-package.py
		// Core package metadata
		m.insert("pkgname",     "pkgname");
		m.insert("pkgver",      "version");
		m.insert("pkgdesc",     "summary");
		m.insert("url",         "homepage");
		m.insert("builddate",   "buildTime");
		m.insert("packager",    "maintainer");
		m.insert("size",        "installedSize");
		m.insert("arch",        "arch");
		m.insert("origin",      "source");
		m.insert("commit",      "commit");
		m.insert("maintainer",  "maintainer");
		m.insert("license",     "license");

        // Dependencies and relationships
        m.insert("depend",      "requires");
        m.insert("conflict",    "conflicts");
        m.insert("provides",    "provides");
        m.insert("replaces",    "replaces");
        m.insert("install_if",  "suggests");
        m.insert("triggers",    "triggers");

        // Priority and versioning
        m.insert("replaces_priority", "replaces_priority");
        m.insert("provider_priority", "provider_priority");

        // Checksums and hashes
        m.insert("datahash",    "sha256");
        m.insert("checksum",    "md5sum");

        m
    };
}

/// PKGINFO field definitions based on APK v2 specification
pub struct PkgInfoField {
    pub name: &'static str,
    pub description: &'static str,
    pub repeatable: bool,
}

lazy_static! {
    pub static ref PKGINFO_FIELDS: std::collections::HashMap<&'static str, PkgInfoField> = {
        let mut m = std::collections::HashMap::new();

        m.insert("pkgname", PkgInfoField {
            name: "pkgname",
            description: "package name",
            repeatable: false,
        });
        m.insert("pkgver", PkgInfoField {
            name: "pkgver",
            description: "package version",
            repeatable: false,
        });
        m.insert("pkgdesc", PkgInfoField {
            name: "pkgdesc",
            description: "package description",
            repeatable: false,
        });
        m.insert("url", PkgInfoField {
            name: "url",
            description: "package url",
            repeatable: false,
        });
        m.insert("builddate", PkgInfoField {
            name: "builddate",
            description: "unix timestamp of the package build date/time",
            repeatable: false,
        });
        m.insert("packager", PkgInfoField {
            name: "packager",
            description: "name (and typically email) of person who built the package",
            repeatable: false,
        });
        m.insert("size", PkgInfoField {
            name: "size",
            description: "the installed-size of the package",
            repeatable: false,
        });
        m.insert("arch", PkgInfoField {
            name: "arch",
            description: "the architecture of the package (ex: x86_64)",
            repeatable: false,
        });
        m.insert("origin", PkgInfoField {
            name: "origin",
            description: "the origin name of the package",
            repeatable: false,
        });
        m.insert("commit", PkgInfoField {
            name: "commit",
            description: "the commit hash from which the package was built",
            repeatable: false,
        });
        m.insert("maintainer", PkgInfoField {
            name: "maintainer",
            description: "name (and typically email) of the package maintainer",
            repeatable: false,
        });
        m.insert("replaces_priority", PkgInfoField {
            name: "replaces_priority",
            description: "replaces priority field for package (integer)",
            repeatable: false,
        });
        m.insert("provider_priority", PkgInfoField {
            name: "provider_priority",
            description: "provider priority for the package (integer)",
            repeatable: false,
        });
        m.insert("license", PkgInfoField {
            name: "license",
            description: "license string for the package",
            repeatable: false,
        });
        m.insert("datahash", PkgInfoField {
            name: "datahash",
            description: "hex-encoded sha256 checksum of the data tarball",
            repeatable: false,
        });

        // Repeatable fields
        m.insert("depend", PkgInfoField {
            name: "depend",
            description: "dependencies for the package",
            repeatable: true,
        });
        m.insert("replaces", PkgInfoField {
            name: "replaces",
            description: "packages this package replaces",
            repeatable: true,
        });
        m.insert("provides", PkgInfoField {
            name: "provides",
            description: "what this package provides",
            repeatable: true,
        });
        m.insert("triggers", PkgInfoField {
            name: "triggers",
            description: "what packages this package triggers on",
            repeatable: true,
        });
        m.insert("install_if", PkgInfoField {
            name: "install_if",
            description: "install this package if these packages are present",
            repeatable: true,
        });

        m
    };
}

/// Unpacks an APK package to the specified directory
pub fn unpack_package<P: AsRef<Path>>(apk_file: P, store_tmp_dir: P) -> Result<()> {
    let apk_file = apk_file.as_ref();
    let store_tmp_dir = store_tmp_dir.as_ref();

    // Create the required directory structure
    fs::create_dir_all(store_tmp_dir.join("fs"))?;
    fs::create_dir_all(store_tmp_dir.join("info/apk"))?;
    fs::create_dir_all(store_tmp_dir.join("info/install"))?;

    // Unpack the APK package
    log::debug!("Unpacking APK package: {}", apk_file.display());
    unpack_apk(apk_file, store_tmp_dir)
        .wrap_err_with(|| format!("Failed to unpack APK package: {}", apk_file.display()))?;

    // Generate filelist.txt
    log::debug!("Creating filelist.txt");
    crate::store::create_filelist_txt(store_tmp_dir)
        .wrap_err_with(|| format!("Failed to create filelist.txt for {}", store_tmp_dir.display()))?;

    // Create scriptlets with proper mapping
    log::debug!("Creating scriptlets");
    create_scriptlets(store_tmp_dir)
        .wrap_err_with(|| format!("Failed to create scriptlets for {}", store_tmp_dir.display()))?;

    // Create package.txt with improved parsing
    log::debug!("Creating package.txt");
    create_package_txt(store_tmp_dir)
        .wrap_err_with(|| format!("Failed to create package.txt for {}", store_tmp_dir.display()))?;

    log::debug!("APK unpacking completed successfully");
    Ok(())
}

/// Unpacks an APK package (concatenated gzip streams containing tar archives)
fn unpack_apk<P: AsRef<Path>>(apk_file: P, store_tmp_dir: &Path) -> Result<()> {
    let apk_file = apk_file.as_ref();
    log::debug!("Unpacking APK package: {}", apk_file.display());

    // Create required directories
    fs::create_dir_all(store_tmp_dir.join("fs"))?;
    fs::create_dir_all(store_tmp_dir.join("info/apk"))?;

    // Open the APK file
    let file = fs::File::open(apk_file)
        .wrap_err_with(|| format!("Failed to open APK file: {}", apk_file.display()))?;

    // Use MultiGzDecoder to handle concatenated gzip streams
    let decoder = MultiGzDecoder::new(file);
    let mut archive = Archive::new(decoder);

    let mut entries_processed = 0;

    // Process tar entries
    for entry_result in archive.entries()? {
        let mut entry = match entry_result {
            Ok(e) => e,
            Err(e) => {
                log::warn!("Error reading tar entry: {}", e);
                continue; // Skip problematic entries
            }
        };

        let path = match entry.path() {
            Ok(p) => p.to_string_lossy().to_string(),
            Err(e) => {
                log::warn!("Error getting path from tar entry: {}", e);
                continue; // Skip entries with invalid paths
            }
        };

        entries_processed += 1;
        log::debug!("Processing tar entry #{}: {}", entries_processed, path);

        // Create the target path - for dot files use just the filename, for others preserve path
        let target_path = if path.starts_with(".") {
            // For dot files, just use the filename part
            let file_name = Path::new(&path)
                .file_name()
                .map(|f| f.to_string_lossy().to_string())
                .unwrap_or_else(|| path.clone());
            store_tmp_dir.join("info/apk").join(file_name)
        } else {
            // For regular files, preserve the full path
            store_tmp_dir.join("fs").join(&path)
        };

        // Ensure parent directory exists
        if let Some(parent) = target_path.parent() {
            if let Err(e) = fs::create_dir_all(parent) {
                log::warn!("Failed to create directory {}: {}", parent.display(), e);
                continue;
            }
        }

        // Extract the file
        if let Err(e) = entry.unpack(&target_path) {
            log::warn!("Failed to extract file {}: {}", path, e);
            continue;
        }
    }

    log::debug!("Successfully unpacked APK package with {} tar entries", entries_processed);
    Ok(())
}

/// Maps APK scriptlet names to common scriptlet names and moves them to info/install/
pub fn create_scriptlets<P: AsRef<Path>>(store_tmp_dir: P) -> Result<()> {
    let store_tmp_dir = store_tmp_dir.as_ref();
    let apk_dir = store_tmp_dir.join("info/apk");
    let install_dir = store_tmp_dir.join("info/install");

    // Mapping from APK scriptlet names to common names
    let scriptlet_mapping: HashMap<&str, Vec<&str>> = [
        (".pre-install", vec!["pre_install.sh", "pre_upgrade.sh"]),
        (".post-install", vec!["post_install.sh", "post_upgrade.sh"]),
        (".pre-deinstall", vec!["pre_uninstall.sh"]),
        (".post-deinstall", vec!["post_uninstall.sh"]),
        (".pre-upgrade", vec!["pre_upgrade.sh"]),
        (".post-upgrade", vec!["post_upgrade.sh"]),
    ].into_iter().collect();

    for (apk_script, common_scripts) in &scriptlet_mapping {
        let apk_script_path = apk_dir.join(apk_script);
        if apk_script_path.exists() {
            for common_script in common_scripts {
                let target_path = install_dir.join(common_script);

                // Copy the script content
                let content = fs::read(&apk_script_path)?;
                fs::write(&target_path, &content)?;

                // Make it executable
                #[cfg(unix)]
                {
                    use std::os::unix::fs::PermissionsExt;
                    let mut perms = fs::metadata(&target_path)?.permissions();
                    perms.set_mode(0o755);
                    fs::set_permissions(&target_path, perms)?;
                }
            }
        }
    }

    Ok(())
}

/// Parses the .PKGINFO file with improved validation and creates package.txt
pub fn create_package_txt<P: AsRef<Path>>(store_tmp_dir: P) -> Result<()> {
    let store_tmp_dir = store_tmp_dir.as_ref();
    let pkginfo_path = store_tmp_dir.join("info/apk/.PKGINFO");

    if !pkginfo_path.exists() {
        return Err(eyre::eyre!(".PKGINFO file not found: {}", pkginfo_path.display()));
    }

    let pkginfo_content = fs::read_to_string(&pkginfo_path)?;
    let mut raw_fields: HashMap<String, Vec<String>> = HashMap::new();

    // Parse the .PKGINFO file with strict format validation
    for (line_num, line) in pkginfo_content.lines().enumerate() {
        let line = line.trim();

        // Skip comments and empty lines
        if line.is_empty() || line.starts_with('#') {
            continue;
        }

        // Strict parsing: must be exactly "key = value" format
        if let Some((key, value)) = line.split_once(" = ") {
            let key = key.trim().to_string();
            let value = value.trim().to_string();

            // Validate field name against known PKGINFO fields
            if let Some(field_def) = PKGINFO_FIELDS.get(key.as_str()) {
                if field_def.repeatable {
                    raw_fields.entry(key).or_insert_with(Vec::new).push(value);
                } else {
                    if raw_fields.contains_key(&key) {
                        log::warn!("Duplicate non-repeatable field '{}' at line {}", key, line_num + 1);
                    }
                    raw_fields.insert(key, vec![value]);
                }
            } else {
                log::warn!("Unknown PKGINFO field '{}' at line {}", key, line_num + 1);
                raw_fields.entry(key).or_insert_with(Vec::new).push(value);
            }
        } else {
            log::warn!("Invalid PKGINFO line format at line {}: {}", line_num + 1, line);
        }
    }

    // Map field names using PACKAGE_KEY_MAPPING and prepare final fields
    let mut package_fields: Vec<(String, String)> = Vec::new();

    for (original_field, values) in raw_fields {
        let mapped_field = PACKAGE_KEY_MAPPING
            .get(original_field.as_str())
            .unwrap_or(&original_field.as_str())
            .to_string();

        // Join multiple values with commas for repeatable fields
        let combined_value = if values.len() > 1 {
            values.join(", ")
        } else {
            values.into_iter().next().unwrap_or_default()
        };

        package_fields.push((mapped_field, combined_value));
    }

    // Use the general store function to save the package.txt file
    crate::store::save_package_txt(package_fields, store_tmp_dir)?;

    Ok(())
}
